/* eslint-disable no-async-promise-executor */
/**
 * 定义一个矩形的圆角。
 * @typedef {Object} Radius
 * @property {number} [tl] - 左上角的圆角半径。
 * @property {number} [tr] - 右上角的圆角半径。
 * @property {number} [br] - 右下角的圆角半径。
 * @property {number} [bl] - 左下角的圆角半径。
 */

/**
 * 定义一个路径对象。
 * @typedef {Object} Path
 * @property {string} method - 当前路径使用的方法。moveTo | lineTo | clip | arc | arcTo
 * @property {number} x - x坐标
 * @property {number} y - y坐标。
 * @property {number} x2 - arcTo第二条切线上一点的横坐标。
 * @property {number} y2 - arcTo第二条切线上一点的纵坐标。
 * @property {number} r - 半径
 * @property {number} sAngle - arc起始角，以弧度计（弧的圆形的三点钟位置是 0 度）
 * @property {number} eAngle - arc结束角，以弧度计。
 */
/**
 * 定义一个矩形的圆角类型。
 * @typedef {number | string | Radius} RectRadius
 */

/**
 * 图像缓存接口，键值对存储。
 * @typedef {Object.<string, any>} IImgCache
 */

/**
 * 图像滤镜缓存接口，键值对存储。
 * @typedef {Object.<string, ImageData>} IImageFilterCache
 */

/**
 * 文本缓存接口，键值对存储。
 * @typedef {Object.<string, any>} ITextCache

/**
 * 旋转数据接口。
 * @interface IJsonDatRotate
 * @property {DrawType.ROTATE} type - 绘制类型，应为 DrawType.ROTATE。
 * @property {number} deg - 旋转角度。
 */

/**
 * 图像数据接口。
 * @interface IJsonDataFilterImage
 * @property {DrawType.IMAGE} type - 绘制类型，应为 DrawType.FILTER_IMAGE
 * @property {string} url - 图像的 URL。
 * @property {number} top - 顶边位置。
 * @property {number} left - 左边位置。
 * @property {RectRadius} [radius] - 图像的圆角。
 * @property {number} width - 图像宽度。
 * @property {number} height - 图像高度。
 * @property {number} [filter] - 图像滤镜。
 * @property {number} [opacity] - 图像透明度。
 */

/**
 * 图像数据接口。
 * @interface IJsonDataImage
 * @property {DrawType.IMAGE} type - 绘制类型，应为 DrawType.IMAGE。
 * @property {string} url - 图像的 URL。
 * @property {number} top - 顶边位置。
 * @property {number} left - 左边位置。
 * @property {RectRadius} [radius] - 图像的圆角。
 * @property {number} width - 图像宽度。
 * @property {number} height - 图像高度。
 * @property {'contain' | 'cover' | 'fill'} [objectFit] - 图像适应方式。
 * @property {number} [filter] - 图像滤镜。
 */

/**
 * 公共文本数据接口。
 * @interface IJsonDataCommonText
 * @property {string} content - 文本内容。
 * @property {number} fontSize - 字体大小。
 * @property {string} [fontFamily] - 字体家族。
 * @property {('normal' | 'italic' | 'oblique')} [fontStyle] - 字体样式。
 * @property {string} [color] - 文本颜色。
 * @property {CanvasTextAlign} [textAlign] - 文本对齐方式。
 * @property {number} [weight] - 字体粗细。
 * @property {CanvasTextBaseline} [textBaseline] - 文本基线。
 * @property {string} [textDecoration] - 文本装饰。
 * @property {number} [letterSpacing] - 字间距。
 * @property {number} [textIndent] - 文本缩进。
 * @property {number} top - 文本的顶部坐标。
 * @property {number} left - 文本的左侧坐标。
 * @property {number} [maxWidth] - 最大宽度。
 * @property {number} [lineHeight] - 行高。
 * @property {number} [maxLine] - 最大行数。
 * @property {string} [eplision] - 省略号。
 */

/**
 * 文本数据接口，继承自 IJsonDataCommonText。
 * @interface IJsonDataText
 * @extends IJsonDataCommonText
 * @property {DrawType.TEXT} type - 绘制类型，应为 DrawType.Text。
 */

/**
 * 文本框数据接口，继承自 IJsonDataCommonText。
 * @interface IJsonDataBoxText
 * @extends IJsonDataCommonText
 * @property {DrawType.TEXT_BOX} type - 绘制类型，应为 DrawType.TEXT_BOX。
 * @property {'top' | 'middle' | 'bottom'} verticalAlign - 垂直对齐方式。
 * @property {number} height - 文本框高度。
 * @property {string} bgColor - 文本框背景色。
 * @property {RectRadius} radius - 文本框圆角。
 */

/**
 * 矩形数据接口。
 * @interface IJsonDataRect
 * @property {DrawType.RECT} [type] - 绘制类型，可选 DrawType.RECT。
 * @property {string} color - 矩形颜色。
 * @property {{startX: number, startY: number, endX: number, endY: number, colors: string[]}} linearGradient - 线性渐变的配置。
 * @property {number} top - 顶边位置。
 * @property {number} left - 左边位置。
 * @property {number} width - 矩形宽度。
 * @property {number} height - 矩形高度。
 * @property {string} fillType - 矩形填充方式 fill填充 stroke描边。
 * @property {number} lineWidth - 矩形描边宽度方式
 * @property {RectRadius} [radius] - 矩形的圆角。
 * @property {number} [opacity] - 透明度。
 */
/**
 * 路径数据接口。
 * @interface IJsonDataPATH
 * @property {DrawType.PATH} [type] - 绘制类型，可选 DrawType.PATH
 * @property {string} color - 路径填充颜色。
 * @property {string} style - 路径填充方式。stroke fill
 * @property {number} lineWidth - 路径填充宽度stroke时存在
 * @property {Path} [path] - 路径数组
 */

/**
 * 虚线数据接口。
 * @interface IJsonDataDashedLine
 * @property {DrawType.DASHED_LINE} [type] - 绘制类型，可选 DrawType.DASHED_LINE
 * @property {string} color - 虚线颜色。
 * @property {number} top - 顶边位置。
 * @property {number} left - 左边位置。
 * @property {number} dashedWidth - 虚线长度
 * @property {number} dashedLength - 间隔长度
 * @property {number} padding - 矩形高度。
 * @property {number} lineWidth - 虚线描边宽度方式
 */

/**
 * 绘制数据类型枚举。
 * @typedef {IJsonDataImage | IJsonDataText | IJsonDataRect | IJsonDatRotate | IJsonDataBoxText | IJsonDataPATH | IJsonDataDashedLine} JsonDataType
 */
const DrawType = {
  RECT: 1,
  IMAGE: 2,
  TEXT: 3,
  TEXT_BOX: 4,
  FILTER_IMAGE: 5,
  ROTATE: 6,
  PATH: 7,
  DASHED_LINE: 8
};

const isInWechatMP = () => {
  return typeof wx == 'object';
};

/**
 * Json2canvas 类用于将 JSON 数据转换并在 Canvas 上绘制。
 * @constructor
 * @param {Object} options - 构造函数的配置选项。
 *   @param {number} [options.width=750] - 默认画布宽度，默认值为 750。
 *   @param {number} [options.height=1334] - 默认画布高度，默认值为 1334。
 *   @param {number} [options.maxMeasureCacheSize=10000] - 文本测量缓存的最大大小，默认值为 10000。
 */
class Json2canvas {
  constructor(options = {}) {
    this.width = options.width || 750;
    this.height = options.height || 1334;
    this.wxMiniCtx = options.wxMiniCtx;
    this.canvasId = options.canvasId;
    this.filterCanvasId = options.filterCanvasId;
    this.canvasMeasureCache = {};
    this.imgCache = {};
    this.imgFilterCache = {};
    this.jsonData = null;
    this.MAX_MEASURE_CACHE_SIZE = options.maxMeasureCacheSize || 10000;
    this.DEFAULT_TEXT_OPTIONS = {
      fontFamily: 'PingFang SC, Microsoft YaHei, Arial Regular',
      color: '#333',
      weight: 400,
      fontStyle: 'normal',
      textAlign: 'left',
      textBaseline: 'top',
      letterSpacing: 0,
      maxLine: -1
    };
  }
  init() {
    if (this.ctx) {
      return Promise.resolve();
    }
    return this.getCanvasCtx(this.canvasId).then(res => {
      this.canvas = res.canvas;
      this.ctx = res.ctx;
      this.initCanvas();
    });
  }

  loadFont(iconUrl) {
    if (isInWechatMP()) {
      return this.loadIconfontByMiniprogram(iconUrl);
    }
    return this.loadFontByWeb(iconUrl);
  }

  loadFontByWeb(iconUrl) {
    return new Promise(resolve => {
      const font = new FontFace('CanvasFont', `url(${iconUrl})`);
      document.fonts.add(font);
      font.load().then(res => {
        resolve(res);
      });
    });
  }

  loadIconfontByMiniprogram(iconUrl) {
    return new Promise(resolve => {
      wx.loadFontFace({
        global: true, // 是否全局生效
        family: 'CanvasFont', // 定义的字体名称
        source: `url("${iconUrl}")`, // 字体资源的地址
        scopes: ['webview', 'native'], // 字体作用范围，设置 native 可在 Canvas 2D 下使用
        success: res => {
          resolve(res);
        },
        fail: res => {
          console.log('loadFontFace-fail', res);
        }
      });
    });
  }

  getCanvasCtx(id) {
    if (isInWechatMP()) {
      return this.getCanvasCtxByMiniprogram(id);
    }
    return this.getCanvasCtxByWeb();
  }
  getCanvasCtxByWeb() {
    return new Promise(resolve => {
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
      resolve({ canvas, ctx });
    });
  }
  // 小程序相关START
  getCanvasCtxByMiniprogram(id) {
    return new Promise(resolve => {
      this.wxMiniCtx
        .createSelectorQuery()
        .select(`#${id}`)
        .fields({ node: true })
        .exec(res => {
          const canvas = res[0].node;
          const ctx = canvas.getContext('2d');
          resolve({ canvas, ctx });
        });
    });
  }
  loadImgByMini(src) {
    return new Promise(async (resolve, reject) => {
      const image = this.canvas.createImage();
      let loadSrc = src;
      if (!src.includes('http')) {
        loadSrc = await this.base64ToSrc(src);
      }
      image.src = loadSrc;
      image.onload = () => {
        if (this.imgCache === null) {
          return reject();
        }
        this.imgCache[src] = image;
        resolve(image);
      };
      image.onerror = () => reject();
    });
  }
  base64ToSrc(base64data) {
    return new Promise((resolve, reject) => {
      const fsm = wx.getFileSystemManager();
      const FILE_BASE_NAME = Date.now() + 'tmp_base64src'; //自定义文件名
      const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
      if (!format) {
        reject('ERROR_BASE64SRC_PARSE');
      }
      const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
      const buffer = wx.base64ToArrayBuffer(bodyData);
      fsm.writeFile({
        filePath,
        data: buffer,
        encoding: 'binary',
        success() {
          resolve(filePath);
        },
        fail() {
          reject('ERROR_BASE64SRC_WRITE');
        }
      });
    });
  }
  getPixelRatioByMini() {
    const dpr = wx.getSystemInfoSync().pixelRatio;
    return dpr;
  }
  //小程序相关END
  getDevicePixelRatio() {
    if (isInWechatMP()) {
      return this.getPixelRatioByMini();
    }
    const dpr = window.devicePixelRatio || 1;
    return dpr;
  }
  initCanvas() {
    if (!this.canvas) {
      return;
    }
    if (!this.ctx) {
      return;
    }
    //获取设备像素比 canvas画布扩大， 保证能够高清显示
    const ratio = this.getDevicePixelRatio();
    this.canvas.width = this.width * ratio;
    this.canvas.height = this.height * ratio;
    this.ctx.scale(ratio, ratio);
  }
  /**
   * 缓存计算得到的文字的宽度
   * @param {string} content
   * @param {number} width
   * @returns
   */
  setMeasureTextCache(content, width) {
    if (this.canvasMeasureCache === null) {
      return;
    }
    if (Object.keys(this.canvasMeasureCache).length > this.MAX_MEASURE_CACHE_SIZE) {
      return;
    }
    this.canvasMeasureCache[encodeURIComponent(content)] = width;
  }
  /**
   * 高斯模糊算法
   * @param {ImageData} imgData
   * @param {number} radius
   * @returns
   */
  gaussBlur(imgData, radius) {
    console.time('gaussBlur');
    radius *= 3; //不知为什么,我的模糊半径是 css中 filter:bulr 值的三倍时效果才一致。
    //Copy图片内容
    const pixes = new Uint8ClampedArray(imgData.data);
    const width = imgData.width;
    const height = imgData.height;
    const gaussMatrix = [];
    let gaussSum = 0,
      x,
      y,
      r,
      g,
      b,
      a,
      i,
      j,
      k,
      w;
    radius = Math.floor(radius);
    const sigma = radius / 3;
    a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
    b = -1 / (2 * sigma * sigma);
    //生成高斯矩阵
    for (i = -radius; i <= radius; i++) {
      gaussMatrix.push(a * Math.exp(b * i * i));
    }
    //x 方向一维高斯运算
    for (y = 0; y < height; y++) {
      for (x = 0; x < width; x++) {
        r = g = b = a = gaussSum = 0;
        for (j = -radius; j <= radius; j++) {
          k = x + j;
          if (k >= 0 && k < width) {
            i = (y * width + k) * 4;
            w = gaussMatrix[j + radius];
            r += pixes[i] * w;
            g += pixes[i + 1] * w;
            b += pixes[i + 2] * w;
            a += pixes[i + 3] * w;
            gaussSum += w;
          }
        }
        i = (y * width + x) * 4;
        //计算加权均值
        imgData.data.set(
          [r, g, b, a].map(v => v / gaussSum),
          i
        );
      }
    }
    pixes.set(imgData.data);
    //y 方向一维高斯运算
    for (x = 0; x < width; x++) {
      for (y = 0; y < height; y++) {
        r = g = b = a = gaussSum = 0;
        for (j = -radius; j <= radius; j++) {
          k = y + j;
          if (k >= 0 && k < height) {
            i = (k * width + x) * 4;
            w = gaussMatrix[j + radius];
            r += pixes[i] * w;
            g += pixes[i + 1] * w;
            b += pixes[i + 2] * w;
            a += pixes[i + 3] * w;
            gaussSum += w;
          }
        }
        i = (y * width + x) * 4;
        imgData.data.set(
          [r, g, b, a].map(v => v / gaussSum),
          i
        );
      }
    }
    console.timeEnd('gaussBlur');
    return imgData;
  }
  /**
   * 根据文字内容，将文字转成对应的换行后的数组
   * @param {IJsonDataText} textOptions
   * @returns {[]string}
   */
  getTextLineBreakArr(textOptions) {
    if (!this.canvas) {
      return [];
    }
    if (!this.ctx) {
      return [];
    }
    const { content, fontSize, fontStyle, weight, fontFamily, maxWidth = 750, letterSpacing = 0, textIndent = 0 } = textOptions;
    const fontInfo = {
      fontStyle,
      weight,
      fontSize,
      fontFamily
    };
    const breakWordArr = `${content}`.split('\n');
    const lineBreakList = [];
    let firstLineIndent = textIndent; //第一行加上文字缩进距离，加完重置为0
    //如果没超过一行，则不显示缩进
    if (this.getTextWidth(content, fontInfo) < maxWidth) {
      firstLineIndent = 0;
    }
    //遇到\n则直接换行
    for (const wordItem of breakWordArr) {
      const textArr = wordItem.split('');
      let textContent = '';
      let textSpacing = letterSpacing;
      for (let i = 0; i < textArr.length; i++) {
        textContent += textArr[i];
        textSpacing += letterSpacing;
        const measureWidth = this.getTextWidth(textContent, fontInfo);
        //如果当前文字总宽度 + 总字间距 > 总宽度，说明需要换行，换行结束需要重置 当前文字内容和总字间距
        if (measureWidth + textSpacing + firstLineIndent > maxWidth) {
          lineBreakList.push(textContent);
          textContent = '';
          firstLineIndent = 0;
          textSpacing = letterSpacing;
        }
      }
      //最后一行如果不为空，则将最后一行放进入
      if (textContent) {
        lineBreakList.push(textContent);
      }
    }
    return lineBreakList;
  }
  loadAllImg() {
    if (!this.jsonData) {
      return Promise.reject();
    }
    const readyLoadImgArr = [];
    for (const item of this.jsonData) {
      if (item.type == DrawType.IMAGE && item.url) {
        readyLoadImgArr.push(this.loadImg(item.url));
      }
    }
    return Promise.all(readyLoadImgArr);
  }
  loadImg(src) {
    if (this.imgCache === null) {
      return Promise.reject();
    }
    if (this.imgCache[src]) {
      return Promise.resolve(this.imgCache[src]);
    }
    if (isInWechatMP()) {
      return this.loadImgByMini(src);
    }
    return this.loadImgByWeb(src);
  }
  loadImgByWeb(src) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      let newSrc = src;
      if (newSrc && (newSrc.includes('http://') || newSrc.includes('https://'))) {
        newSrc = src.includes('?') ? `${newSrc}&v=2` : `${newSrc}?v=2`;
      }
      img.crossOrigin = 'anonymous';
      img.src = newSrc;
      img.onload = () => {
        if (this.imgCache === null) {
          return reject();
        }
        this.imgCache[src] = img;
        resolve(img);
      };
      img.onerror = () => {
        console.log('loaderr', src);
        reject();
      };
    });
  }

  /**
   * 设置图片object-fic所需要的值
   * 图片宽高和绘制图片内容，得到图片剪切相关属性
   * @param {IJsonDataImage} imgOptions
   * @param {number} imgWdith
   * @param {number} imgHeight
   * @returns
   */
  getDrawImageInfoByObjectFit(imgOptions, imgWdith, imgHeight) {
    const { top = 0, left = 0, width = 0, height = 0, objectFit = 'fill' } = imgOptions;
    let sx = -1;
    let sy = -1;
    let sw = -1;
    let sh = -1;
    let dw = width;
    let dh = height;
    let dx = left;
    let dy = top;
    //原图宽高比
    const imgRatio = imgWdith / imgHeight;
    //绘制出来的宽高比
    const imgCanvasRatio = width / height;
    if (imgRatio > imgCanvasRatio) {
      if (objectFit == 'contain') {
        //原图宽高比 > 绘制宽高比，说明当宽度一样的时候，原图的高度 < 绘制的高度
        dw = width;
        dh = dw / imgRatio;
        dx = left;
        dy = top + (height - dh) / 2;
      } else if (objectFit == 'cover') {
        //原图宽高比 > 绘制宽高比，说明当宽度一样的时候，原图的高度 < 绘制的高度,所以需要将原图的高度拉到绘制的高度，宽度剪切
        sh = imgHeight;
        sw = sh * imgCanvasRatio;
        sx = (imgWdith - sw) / 2;
        sy = 0;
      }
    } else {
      if (objectFit == 'contain') {
        //原图宽高比 < 绘制宽高比，说明当宽度一样的时候，原图的高度 > 绘制的高度，
        dh = height;
        dw = height * imgRatio;
        dx = left + (width - dw) / 2;
        dy = top;
      } else if (objectFit == 'cover') {
        //原图宽高比 < 绘制宽高比，说明当宽度一样的时候，原图的高度 > 绘制的高度，所以需要将原图宽度拉到绘制的高度，高度剪切
        sw = imgWdith;
        sh = sw / imgCanvasRatio;
        sx = 0;
        sy = (imgHeight - sh) / 2;
      }
    }
    return {
      sx,
      sy,
      sw,
      sh,
      dx,
      dy,
      dw,
      dh
    };
  }
  parseRadius(radius, width) {
    if (typeof radius === 'number' || (typeof radius === 'string' && !radius.endsWith('%'))) {
      let resRadius = radius * 1;
      resRadius = resRadius > width / 2 ? resRadius / 2 : resRadius;
      return {
        tl: resRadius,
        tr: resRadius,
        br: resRadius,
        bl: resRadius
      };
    }
    if (typeof radius === 'string' && radius.endsWith('%')) {
      const percent = parseFloat(radius.slice(0, -1)) / 100;
      let resRadius = percent * width;
      resRadius = resRadius > width / 2 ? resRadius / 2 : resRadius;
      return {
        tl: resRadius,
        tr: resRadius,
        br: resRadius,
        bl: resRadius
      };
    }
    return radius;
  }
  /**
   * 剪切圆角
   * @param {number} x
   * @param {*} y
   * @param {*} w
   * @param {*} h
   * @param {*} radius
   * @param {*} isClip 是否剪切
   * @returns
   */
  drawArcRectPath(x, y, w, h, radius, isClip = 1) {
    if (!this.ctx) {
      return;
    }
    const { tl = 0, tr = 0, br = 0, bl = 0 } = this.parseRadius(radius, w);
    this.ctx.beginPath();
    this.ctx.moveTo(x + tl, y);
    this.ctx.lineTo(x + w - tr, y);
    this.ctx.arcTo(x + w, y, x + w, y + tr, tr);
    this.ctx.lineTo(x + w, y + h - br);
    this.ctx.arcTo(x + w, y + h, x + w - br, y + h, br);
    this.ctx.lineTo(x + bl, y + h);
    this.ctx.arcTo(x, y + h, x, y + h - bl, bl);
    this.ctx.lineTo(x, y + tl);
    this.ctx.arcTo(x, y, x + tl, y, tl);
    this.ctx.closePath();
    // 剪切当前路径区域
    if (isClip == 1) {
      this.ctx.clip();
    }
  }

  /**
   * 绘制高斯模糊图片
   * @param {IJsonDataFilterImage} imgOptions
   * @returns
   */
  async drawFilterImage(imgOptions) {
    if (!this.ctx) {
      return;
    }
    const { url, top = 0, left = 0, width = 0, height = 0, radius = 0, filter = 0, opacity } = imgOptions;
    if (!this.imgCache) {
      return;
    }
    if (!this.imgFilterCache) {
      return;
    }
    if (!this.imgCache[url]) {
      return;
    }
    this.ctx.save();
    if (opacity) {
      this.ctx.globalAlpha = opacity;
    }
    //如果本身支持高斯模糊，则直接使用canvas模糊,不用算法模糊，算法模糊耗时1s以上，图片越大耗时越长
    if (this.ctx.filter && this.ctx.filter != 'none') {
      this.ctx.filter = `blur(${filter}px)`;
      if (radius != 0) {
        this.drawArcRectPath(left, top, width, height, radius);
      }
      this.ctx.drawImage(this.imgCache[url], left, top, width, height);
      this.ctx.restore();
      return;
    }

    let { canvas: filterCanvas, ctx: filterCtx } = await this.getCanvasCtx(this.filterCanvasId);
    if (!filterCtx) {
      return;
    }
    filterCanvas.width = width;
    filterCanvas.height = height;
    filterCtx.drawImage(this.imgCache[url], -10, -10, width, height);
    let filterData = null;
    if (this.imgFilterCache[url]) {
      filterData = this.imgFilterCache[url];
    } else {
      const originImageData = filterCtx.getImageData(0, 0, width, height);
      //进行高斯模糊
      filterData = this.gaussBlur(originImageData, filter);
      this.imgFilterCache[url] = filterData;
    }
    //绘制模糊图像
    filterCtx.putImageData(filterData, 0, 0);
    if (radius != 0) {
      this.drawArcRectPath(left, top, width, height, radius);
    }
    this.ctx.drawImage(filterCanvas, left, top, width, height);
    this.ctx.restore();
    filterCanvas = null;
    filterCtx = null;
  }
  /**
   * 绘制矩形
   * @param {IJsonDataRect} rectOptions
   * @returns
   */
  drawRect(rectOptions) {
    if (!this.ctx) {
      return;
    }
    const { color, linearGradient, top = 0, left = 0, width = 0, height = 0, radius = 0, fillType = 'fill', lineWidth = 1, opacity } = rectOptions;
    this.ctx.save();
    if (opacity) {
      this.ctx.globalAlpha = opacity;
    }
    let fillStyle = color;
    if (linearGradient) {
      // startX,startY,endX,endY对应渐变的起始点和结束点
      const gradient = this.ctx.createLinearGradient(linearGradient.startX, linearGradient.startY, linearGradient.endX, linearGradient.endY);
      const colorArr = linearGradient.colors || [];
      const colorLength = colorArr.length;
      for (let i = 0; i < colorLength; i++) {
        gradient.addColorStop(i / colorLength, colorArr[i]);
      }
      fillStyle = gradient;
    }
    if (fillType == 'fill') {
      this.ctx.fillStyle = fillStyle;
    } else {
      this.ctx.strokeStyle = fillStyle;
      this.ctx.lineWidth = lineWidth;
    }
    this.drawArcRectPath(left, top, width, height, radius, 0);
    if (fillType == 'fill') {
      this.ctx.fill();
    } else {
      this.ctx.stroke();
    }

    this.ctx.restore();
  }
  /**
   * 绘制图片
   * @param {IJsonDataImage} imgOptions
   * @returns
   */
  async drawImage(imgOptions) {
    if (!this.ctx) {
      return;
    }
    const { url, top = 0, left = 0, width = 0, height = 0, radius = 0, objectFit, filter } = imgOptions;
    if (!this.imgCache) {
      return;
    }
    if (!this.imgCache[url]) {
      return;
    }
    if (objectFit == 'contain' && filter) {
      await this.drawFilterImage(imgOptions);
    }
    this.ctx.save();
    if (radius != 0) {
      this.drawArcRectPath(left, top, width, height, radius);
    }
    const { sx, sy, sw, sh, dx, dy, dw, dh } = this.getDrawImageInfoByObjectFit(imgOptions, this.imgCache[url].width, this.imgCache[url].height);
    if (sx == -1) {
      this.ctx.drawImage(this.imgCache[url], dx, dy, dw, dh);
    } else {
      this.ctx.drawImage(this.imgCache[url], sx, sy, sw, sh, dx, dy, dw, dh);
    }
    this.ctx.restore();
  }
  /**
   * 绘制文本
   * @param {IJsonDataText} textOptions
   * @returns
   */
  drawText(textOptions) {
    if (!this.ctx) {
      return;
    }
    this.ctx.save();
    textOptions = Object.assign(Object.assign({}, this.DEFAULT_TEXT_OPTIONS), textOptions);
    let {
      fontSize,
      weight,
      fontFamily,
      fontStyle,
      color,
      textAlign,
      textBaseline = '',
      textDecoration = '',
      textIndent = 0,
      top = 0,
      left = 0,
      maxWidth = this.width,
      maxLine = this.DEFAULT_TEXT_OPTIONS.maxLine
    } = textOptions;
    const font = `${fontStyle} ${weight} ${fontSize}px "${fontFamily}"`;
    this.ctx.fillStyle = color;
    this.ctx.textAlign = textAlign;
    this.ctx.textBaseline = textBaseline;
    this.ctx.font = font;
    const textSplitArr = this.getTextLineBreakArr(textOptions);
    const line = textSplitArr.length > maxLine && maxLine > 0 ? maxLine : textSplitArr.length;
    //当前文字行数超过最大设置行数并且需要加textOptions
    const isShowEplision = maxLine > 0 && textSplitArr.length > maxLine && textOptions.eplision;
    let lineTop = top;
    const lineHeight = textOptions.lineHeight || fontSize;
    for (let i = 0; i < line; i++) {
      let textStr = textSplitArr[i];
      let textX = left;
      //如果是最后一行，看是否有要加...，有的话需要把字符串最后两个值改成...
      if (i == line - 1 && isShowEplision) {
        textStr = textStr.substring(0, textStr.length - 2) + '...';
      }
      if (i == 0 && textIndent) {
        textX = left + textIndent;
      }
      this.ctx.fillText(textStr, textX, lineTop, maxWidth);
      if (textDecoration) {
        this.drawTextLine(textOptions);
      }
      lineTop += lineHeight;
    }
    this.ctx.restore();
  }
  /**
   * 绘制再矩形框内的文本
   * @param {IJsonDataBoxText} textOptions
   * @returns
   */
  drawBoxText(textOptions) {
    if (!this.ctx) {
      return;
    }
    this.ctx.save();
    textOptions = Object.assign(Object.assign({}, this.DEFAULT_TEXT_OPTIONS), textOptions);
    const { fontSize, weight, fontFamily, fontStyle, color, bgColor, radius, textAlign, top = 0, left = 0, maxWidth = this.width, letterSpacing = 0, verticalAlign } = textOptions;
    const font = `${fontStyle} ${weight} ${fontSize}px ${fontFamily}`;
    const fontInfo = {
      fontStyle,
      weight,
      fontSize,
      fontFamily
    };
    let height = textOptions.height;
    const lineHeight = textOptions.lineHeight || fontSize;
    const textSplitArr = this.getTextLineBreakArr(textOptions);
    const line = textSplitArr.length;
    //如果传入的高度小于绘制文字所需要的高度，则高度需要重置为最小需要的高度
    if (height < lineHeight * line) {
      height = lineHeight * line;
    }
    if (bgColor) {
      this.drawRect({
        color: bgColor,
        top: top,
        left: left,
        width: maxWidth,
        height: height,
        radius: radius
      });
    }
    this.ctx.fillStyle = color;
    this.ctx.textAlign = 'left';
    this.ctx.textBaseline = verticalAlign;
    this.ctx.font = font;
    let lineTop = top;
    if (verticalAlign == 'top') {
      lineTop = top;
    } else if (verticalAlign == 'middle') {
      //顶部位置 = （容器高度 - 行高 * (总共几行 - 1)） / 2
      lineTop = top + (height - lineHeight * (line - 1)) / 2;
    } else if (verticalAlign == 'bottom') {
      //顶部位置 = （容器高度 - 行高 * 总共几行） / 2
      lineTop = top + (height - lineHeight * (line - 1));
    }
    let lineLeft = left;
    for (let i = 0; i < line; i++) {
      const textStr = textSplitArr[i];
      if (textAlign == 'left') {
        lineLeft = left;
      } else if (textAlign == 'center') {
        const textWidth = this.getTextWidth(textStr, fontInfo);
        //最左侧位置 = 整个宽度 - （文字宽度+文字总间距）/2
        let offsetX = (maxWidth - (textWidth + letterSpacing * textStr.length)) / 2;
        offsetX = offsetX > 0 ? offsetX : 0;
        lineLeft = left + offsetX;
      } else if (textAlign == 'right') {
        const textWidth = this.getTextWidth(textStr, fontInfo);
        //最左侧位置 = 整个宽度 - （文字宽度+文字总间距）
        let offsetX = maxWidth - (textWidth + letterSpacing * textStr.length);
        offsetX = offsetX > 0 ? offsetX : 0;
        lineLeft = left + offsetX;
      }
      for (let j = 0; j < textStr.length; j++) {
        const char = textStr[j];
        const charWidth = this.getTextWidth(char, fontInfo);
        this.ctx.fillText(char, Math.round(lineLeft), Math.round(lineTop), Math.round(maxWidth));
        lineLeft += letterSpacing + charWidth;
      }
      lineTop += lineHeight;
    }
    this.ctx.restore();
  }
  /**
   * 绘制文字对应的上划线或者下划线
   * @param {*} textOptions
   * @returns
   */
  drawTextLine(textOptions) {
    if (!this.ctx) {
      return;
    }
    textOptions = Object.assign(Object.assign({}, this.DEFAULT_TEXT_OPTIONS), textOptions);
    const { fontSize, fontStyle, color, top = 0, left = 0, textDecoration = '', weight, fontFamily } = textOptions;
    const fontInfo = {
      fontStyle,
      weight,
      fontSize,
      fontFamily
    };
    if (textDecoration === 'underline') {
      this.drawRect({
        color: color,
        top: top + fontSize * 1.2,
        left: left - 1,
        width: this.getTextWidth(textOptions.content, fontInfo) + 3,
        height: 1
      });
    } else if (textDecoration === 'line-through') {
      this.drawRect({
        color: color,
        top: top + fontSize * 0.5,
        left: left - 1,
        width: this.getTextWidth(textOptions.content, fontInfo) + 3,
        height: 1
      });
    }
  }
  /**
   * 通过路径绘制多边形图案
   * @param {IJsonDataPATH} pathOptions
   * @returns
   */
  drawPath(pathOptions) {
    if (!this.ctx) {
      return;
    }
    this.ctx.save();
    const { color, style, lineWidth, path } = pathOptions;
    if (!path) {
      return;
    }
    if (!path.length) {
      return;
    }
    if (style == 'stroke') {
      this.ctx.lineWidth = lineWidth | 1;
      this.ctx.strokeStyle = color;
    } else {
      this.ctx.fillStyle = color;
    }

    this.ctx.beginPath();
    for (let item of path) {
      const { method, x, y, x2, y2, r } = item;
      if (method == 'moveTo' || method == 'lineTo') {
        this.ctx[method](x, y);
      } else if (method == 'clip') {
        this.ctx[method]();
      } else if (method == 'arc') {
        this.ctx[method](x, y, r);
      } else if (method == 'arcTo') {
        this.ctx[method](x, y, x2, y2, r);
      }
    }
    if (style == 'fill') {
      this.ctx.fill();
    }
    this.ctx.restore();
  }
  /**
   * 绘制虚线
   * @param {IJsonDataDashedLine} lineOptions
   * @returns
   */
  drawDashedLine(lineOptions) {
    if (!this.ctx) {
      return;
    }
    this.ctx.save();
    const { color, lineWidth, top = 0, left = 0, dashedWidth, dashedLength = 5, padding = 5 } = lineOptions;
    this.ctx.setLineDash([dashedLength, padding]); // [虚线长度, 间隔长度]
    this.ctx.lineWidth = lineWidth;
    this.ctx.strokeStyle = color;
    // 开始绘制路径
    this.ctx.beginPath();
    this.ctx.moveTo(left, top);
    this.ctx.lineTo(left + dashedWidth, top);
    this.ctx.stroke();
    this.ctx.restore();
  }
  /**
   * 旋转画布
   * @param {number} deg
   * @returns
   */
  rotateCanvas(deg) {
    if (!this.ctx) {
      return;
    }
    this.ctx.rotate((deg * Math.PI) / 180);
  }
  /**
   * 初始化 JSON 数据，准备在 Canvas 上进行绘制。
   * @param {JsonDataType[]} jsonData - 要绘制的 JSON 数据数组。
   * @returns {Promise<void>} - 如果初始化成功，返回一个 Promise，当所有图像资源加载完成后解析。
   * @throws {Promise<undefined>} - 如果没有有效的 canvas 或 ctx，抛出一个 Promise 错误。
   */
  initJson(jsonData) {
    if (!this.canvas) {
      return Promise.reject();
    }
    if (!this.ctx) {
      return Promise.reject();
    }
    this.jsonData = jsonData;
    return this.loadAllImg();
  }
  resetCanvasSize(width, height) {
    this.width = width;
    this.height = height;
    this.initCanvas();
  }
  startDraw() {
    return new Promise(async (resolve, reject) => {
      if (!this.canvas) {
        return reject();
      }
      if (!this.jsonData) {
        return reject();
      }
      for (const item of this.jsonData) {
        if (item.type == DrawType.RECT) {
          this.drawRect(item);
        } else if (item.type == DrawType.IMAGE) {
          await this.drawImage(item);
        } else if (item.type == DrawType.TEXT) {
          this.drawText(item);
        } else if (item.type == DrawType.TEXT_BOX) {
          this.drawBoxText(item);
        } else if (item.type == DrawType.ROTATE) {
          this.rotateCanvas(item.deg);
        } else if (item.type == DrawType.FILTER_IMAGE) {
          this.drawFilterImage(item);
        } else if (item.type == DrawType.PATH) {
          this.drawPath(item);
        } else if (item.type == DrawType.DASHED_LINE) {
          this.drawDashedLine(item);
        }
      }
      resolve();
    });
  }
  toDataUrl(type = 'image/png', encoderOptions = 0.92) {
    return this.canvas.toDataURL(type, encoderOptions);
  }
  toBlob(type = 'image/png', encoderOptions = 0.92) {
    return new Promise(resolve => {
      this.canvas.toBlob(
        blob => {
          resolve(blob);
        },
        type,
        encoderOptions
      );
    });
  }
  dataURItoBlob(base64Data) {
    var byteString;
    if (base64Data.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(base64Data.split(',')[1]);
    } else {
      byteString = unescape(base64Data.split(',')[1]);
    }
    var mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ia], { type: mimeString });
  }
  /**
   * 解析unicode字符串
   * @param {string} str
   * @returns 返回解析后的字符串
   */
  encodeWithUnicode(str) {
    return str.replace(/[\u0080-\uFFFF]/g, function(char) {
      return '%u' + char.charCodeAt(0).toString(16).toUpperCase();
    });
  }
  /**
   * 根据文字内容获取文字的宽度
   * @param {string} text
   * @param {fontInfo} fontInfo
   * @param {fontInfo.fontStyle} 字体样式
   * @param {fontInfo.weight} 字重
   * @param {fontInfo.fontSize} 字体大小
   * @param {fontInfo.fontFamily} 字体
   * @returns {number} 文字宽度
   */
  getTextWidth(text, fontInfo) {
    if (!this.canvas) {
      return 0;
    }
    if (!this.ctx) {
      return 0;
    }
    if (!text) {
      return 0;
    }
    if (this.canvasMeasureCache === null) {
      return 0;
    }
    const {fontStyle, weight, fontSize, fontFamily} = {...this.DEFAULT_TEXT_OPTIONS, ...fontInfo};
    const font = `${fontStyle} ${weight} ${fontSize}px ${fontFamily}`;
    //替换\u 为 %u 不然encodeURIComponent 会报错
    let replaceUnicode = this.encodeWithUnicode(text);
    if (this.canvasMeasureCache[encodeURIComponent(`${replaceUnicode}-${font}`)]) {
      return this.canvasMeasureCache[encodeURIComponent(`${replaceUnicode}-${font}`)];
    }
    this.ctx.save();
    this.ctx.font = font;
    const width = this.ctx.measureText(text).width;
    this.ctx.restore();
    this.setMeasureTextCache(`${replaceUnicode}-${font}`, width);
    return width;
  }
  /**
   * 根据文字内容，截取文字最大宽度对应的字符串
   * @param {string} text
   * @param {IJsonDataCommonText} textOptions
   * @returns {string}
   * @example
   *
   * let str ='天天天天天天天天天天天天'
   * let resStr = str.getSplitTextByMaxWidth(str,)
   */
  getSplitTextByMaxWidth(text, textOptions) {
    if (!this.canvas) {
      return '';
    }
    if (!this.ctx) {
      return '';
    }
    if (!text) {
      return '';
    }
    textOptions = { ...this.DEFAULT_TEXT_OPTIONS, ...textOptions };
    const { fontSize, weight, fontFamily, fontStyle, maxWidth = this.width } = textOptions;
    const fontInfo = {
      fontStyle,
      weight,
      fontSize,
      fontFamily
    };
    const textArr = text.split('');
    let textStr = '';
    for (let i = 0; i < textArr.length; i++) {
      textStr += textArr[i];
      const textStrWidth = this.getTextWidth(textStr, fontInfo);
      if (textStrWidth >= maxWidth) {
        break;
      }
    }
    return textStr;
  }
  /**
   * 根据文字内容获取文字高度
   * @param {IJsonDataText} textOptions
   * @returns
   */
  getTextHeight(textOptions) {
    if (!this.canvas) {
      return 0;
    }
    if (!this.ctx) {
      return 0;
    }
    textOptions = Object.assign(Object.assign({}, this.DEFAULT_TEXT_OPTIONS), textOptions);
    const { fontSize, maxLine } = textOptions;
    const lineHeight = textOptions.lineHeight || fontSize;
    const arr = this.getTextLineBreakArr(textOptions);
    let totalHieght = arr.length * lineHeight;
    if (maxLine && maxLine > 1 && lineHeight * maxLine < totalHieght) {
      totalHieght = lineHeight * maxLine;
    }
    return totalHieght;
  }
  destroy() {
    this.imgCache = null;
    this.imgFilterCache = null;
    this.canvasMeasureCache = null;
    this.canvas = null;
    this.ctx = null;
  }
}
export { Json2canvas, DrawType };
